﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Vasuleg.UIDrive
{
    public abstract class UIDriveNode
    {
        private GameObject _targetGameObject;

        public GameObject TargetGameObject => _targetGameObject ??= _target switch
        {
            GameObject go => go,
            Component comp => comp.gameObject,
            _ => _targetGameObject
        };

        private Transform _targetTransform;

        public Transform TargetTransform => _targetTransform ??= _target switch
        {
            GameObject go => go.transform,
            Component comp => comp.transform,
            _ => _targetTransform
        };


        private Object _target;

        public Object Target
        {
            get => _target;
            set => OnTargetChange(value);
        }

        public UIDrive UIDrive { get; }
        
        private readonly List<MemberPathNode> _listenerPathList = new();
        private readonly List<object> _lastValue = new();

        protected UIDriveNode(UIDrive uiDrive)
        {
            UIDrive = uiDrive;
        }

        protected void AddMemberPathNode(MemberPathNode memberPathNode)
        {
            _listenerPathList.Add(memberPathNode);
            _lastValue.Add(null);
            UIDrive.AddListenerMemberInfo(memberPathNode.MemberPath[0]);
        }
        
        protected void AddRangeMemberPathNode(IEnumerable<MemberPathNode> memberPathNodes)
        {
            var startIndex = _listenerPathList.Count;
            _listenerPathList.AddRange(memberPathNodes);
            var endCount = _listenerPathList.Count;
            for (var i = startIndex; i < endCount; i++)
            {
                _lastValue.Add(null);
                UIDrive.AddListenerMemberInfo(_listenerPathList[i].MemberPath[0]);
            }
        }


        private void OnTargetChange(Object value)
        {
            if (_target == value)
            {
                return;
            }

            _targetGameObject = null;
            _target = value;
        }

        internal abstract void Awake();

        internal void NoticeData(NoticeDataContext context)
        {
            var hasTrigger = _listenerPathList.Any(pathNode => context.TriggerSet.Contains(pathNode.MemberPath[0]));
            if (!hasTrigger)
            {
                return;
            }

            var hasValueChange = false;
            for (var i = 0; i < _listenerPathList.Count; i++)
            {
                var newVal = _listenerPathList[i].Get();
                if (!Equals(_lastValue[i], newVal))
                {
                    _lastValue[i] = newVal;
                    hasValueChange = true;
                }
            }
            if (!hasValueChange)
            {
                return;
            }

            OnDriveNoticeDataChange(context);
        }

        protected abstract void OnDriveNoticeDataChange(NoticeDataContext context);
    }

    public abstract class UIDriveNode<T> : UIDriveNode
    {
        private readonly Action<T> _targetAssignFunc;
        private T _lastValue;
        private bool _isFirst = true;
        
        
        public T DefaultValue { get; set; }

        protected UIDriveNode(UIDrive uiDrive, Action<T> targetAssignFunc) : base(uiDrive)
        {
            _targetAssignFunc = targetAssignFunc;
        }

        protected void TargetAssign(T value)
        {
            if (_isFirst || !Equals(_lastValue, value))
            {
                _isFirst = false;
                _targetAssignFunc(_lastValue = value);
            }
        }

        public UIDriveNode<T> SetDefaultValue(T value)
        {
            DefaultValue = value;
            return this;
        }
    }
}